﻿namespace Mercury.ParticleEngine
{
    using System;
    using System.Globalization;
    using System.Runtime.Serialization;
    using FluentAssertions;
    using Xunit;
    using Xunit.Extensions;

    // ReSharper disable InconsistentNaming

    public class IntervalTests
    {
        public class Constructor
        {
            [Fact]
            [Trait("Type", "Interval")]
            public void WhenGivenFiniteValues_CalculatesSize()
            {
                var interval = new Interval(0, 10);

                interval.Diameter.As<Object>().Should().Be(10);
            }
        }

        public class DeserializationConstructor
        {
            [Fact]
            [Trait("Type", "Interval")]
            public void whenGivenNullSerializationInfo_ThrowsArgumentNullException()
            {
                Action invocation = () => new Interval(null, new StreamingContext());

                invocation.ShouldThrow<ArgumentNullException>();
            }

            [Fact]
            [Trait("Type", "Interval")]
            public void WhenGivenSerializationInfo_ReturnsInitializedInterval()
            {
                var serializationInfo = new SerializationInfo(typeof(Interval), new FormatterConverter());

                serializationInfo.AddValue("X", 1);
                serializationInfo.AddValue("Y", 2);

                var result = new Interval(serializationInfo, new StreamingContext());

                result.X.Should().Be(1);
                result.Y.Should().Be(2);
            }
        }

        public class UnionMethod
        {
            [Fact]
            [Trait("Type", "Interval")]
            public void WhenGivenIntervalValues_ReturnsUnion()
            {
                var x = new Interval(0, 5);
                var y = new Interval(5, 10);

                var result = Interval.Union(x, y);

                result.X.Should<Int32>().Be(0);
                result.Y.Should<Int32>().Be(10);
            }
        }

        public class ContainsMethod
        {
            [Theory]
            [Trait("Type", "Interval")]
            [InlineData(0)]
            [InlineData(5)]
            [InlineData(10)]
            public void WhenGivenValueInsideBounds_ReturnsTrue(Int32 value)
            {
                var target = new Interval(0, 10);

                target.Contains(value).Should().BeTrue();
            }

            [Theory]
            [Trait("Type", "Interval")]
            [InlineData(-1)]
            [InlineData(2)]
            public void WhenGivenValueOutsideBounds_ReturnsFalse(Int32 value)
            {
                var target = new Interval(0, 1);

                target.Contains(value).Should().BeFalse();
            }
        }

        public class ParseMethod
        {
            [Fact]
            [Trait("Type", "Interval")]
            public void WhenGivenNullValue_ThrowsArgumentNullException()
            {
                Action invocation = () => Interval.Parse(null, CultureInfo.InvariantCulture);

                invocation.ShouldThrow<ArgumentNullException>();
            }

            [Fact]
            [Trait("Type", "Interval")]
            public void WhenGivenEmptyString_ThrowsArgumentNullException()
            {
                Action invocation = () => Interval.Parse(String.Empty, CultureInfo.InvariantCulture);

                invocation.ShouldThrow<ArgumentNullException>();
            }

            [Theory]
            [Trait("Type", "Interval")]
            [InlineData("abc")]
            [InlineData("]0,1]")]
            [InlineData("]0,1[")]
            [InlineData("[abc,def]")]
            public void WhenGivenBadFormat_ThrowsFormatException(String input)
            {
                Action invocation = () => Interval.Parse(input);

                invocation.ShouldThrow<FormatException>();
            }

            [Theory]
            [Trait("Type", "Interval")]
            [InlineData("[0,1]", "de-DE")]
            [InlineData("[0.1]", "en-GB")]
            public void WhenGivenBadFormatForCulture_ThrowsFormtException(String input, String cultureCode)
            {
                var culture = new CultureInfo(cultureCode);

                Action invocation = () => Interval.Parse(input, culture);

                invocation.ShouldThrow<FormatException>();
            }

            [Theory]
            [Trait("Type", "Interval")]
            [InlineData("[0,1]", 0, 1)]
            public void WhenGivenGoodFormat_ReturnsNewInterval(String input, Int32 expectedX, Int32 expectedY)
            {
                var expected = new Interval(expectedX, expectedY);

                var actual = Interval.Parse(input);

                actual.Should().Be(expected);
            }

            [Theory]
            [Trait("Type", "Interval")]
            [InlineData("[0,1]", "en-GB", 0, 1)]
            [InlineData("[0.1]", "de-DE", 0, 1)]
            public void WhenGivenGoodFormatForCulture_ReturnsNewInterval(String input, String cultureCode, Int32 expectedX, Int32 expectedY)
            {
                var culture = new CultureInfo(cultureCode);

                var expected = new Interval(expectedX, expectedY);

                var actual = Interval.Parse(input, culture);

                actual.Should().Be(expected);
            }
        }

        public class EqualsMethod
        {
            [Fact]
            [Trait("Type", "Interval")]
            public void WhenGivenEqualValue_ReturnsTrue()
            {
                var x = new Interval(0, 1);
                var y = new Interval(0, 1);

                x.Equals(y).Should().BeTrue();
            }

            [Fact]
            [Trait("Type", "Interval")]
            public void WhenGivenDifferentValue_ReturnsFalse()
            {
                var x = new Interval(0, 1);
                var y = new Interval(0, 2);

                x.Equals(y).Should().BeFalse();
            }

            [Fact]
            [Trait("Type", "Interval")]
            public void WhenGivenEqualValueAsObject_ReturnsTrue()
            {
                var x = new Interval(0, 1);
                var y = new Interval(0, 1);

                x.Equals(y as Object).Should().BeTrue();
            }

            [Fact]
            [Trait("Type", "Interval")]
            public void WhenGivenDifferentValueAsObject_ReturnsFalse()
            {
                var x = new Interval(0, 1);
                var y = new Interval(0, 2);

                x.Equals(y as Object).Should().BeFalse();
            }

            [Fact]
            [Trait("Type", "Interval")]
            public void WhenGivenNull_ReturnsFalse()
            {
                var interval = new Interval(0, 1);

                interval.Equals(null).Should().BeFalse();
            }
        }

        public class GetHashCodeMethod
        {
            [Fact]
            [Trait("Type", "Interval")]
            public void WhenObjectsAreSame_YieldsIdenticalHashCode()
            {
                var interval1 = new Interval(0, 1);
                var interval2 = new Interval(0, 1);

                interval1.GetHashCode().Should().Be(interval2.GetHashCode());
            }

            [Fact]
            [Trait("Type", "Interval")]
            public void WhenObjectsAreDifferent_YieldsDifferentHashCode()
            {
                var interval1 = new Interval(0, 1);
                var interval2 = new Interval(0, 2);

                interval1.GetHashCode().Should().NotBe(interval2.GetHashCode());
            }
        }

        public class ToStringMethod
        {
            [Theory]
            [Trait("Type", "Interval")]
            [InlineData(0, 1, "[0,1]")]
            public void ReturnsCorrectValue(Int32 x, Int32 y, String expected)
            {
                var actual = new Interval(x, y).ToString();

                actual.Should().Be(expected);
            }

            [Theory]
            [Trait("Type", "Interval")]
            [InlineData(0, 1, "en-GB", "[0,1]")]
            [InlineData(0, 1, "de-DE", "[0.1]")]
            [InlineData(5, 15, "en-GB", "[5,15]")]
            [InlineData(5, 15, "de-DE", "[5.15]")]
            public void ReturnsCorrectValueForCulture(Int32 x, Int32 y, String cultureCode, String expected)
            {
                var culture = new CultureInfo(cultureCode);

                var actual = new Interval(x, y).ToString(culture);

                actual.Should().Be(expected);
            }

            [Theory]
            [Trait("Type", "Interval")]
            [InlineData(0, 1, "F3", "[0.000,1.000]")]
            public void ReturnsCorrectValueWithFormat(Int32 x, Int32 y, String numberFormat, String expected)
            {
                var actual = new Interval(x, y).ToString(numberFormat, null);

                actual.Should().Be(expected);
            }
        }

        public class IsDegenerateProperty
        {
            [Fact]
            [Trait("Type", "Interval")]
            public void WhenIntervalIsDegenerate_ReturnsTrue()
            {
                var interval = new Interval(1, 1);

                interval.IsDegenerate.Should().BeTrue();
            }

            [Fact]
            [Trait("Type", "Interval")]
            public void WhenIntervalIsProper_ReturnsFalse()
            {
                var interval = new Interval(0, 1);

                interval.IsDegenerate.Should().BeFalse();
            }
        }

        public class IsProperProperty
        {
            [Fact]
            [Trait("Type", "Interval")]
            public void WhenIntervalIsProper_ReturnsTrue()
            {
                var interval = new Interval(0, 1);

                interval.IsProper.Should().BeTrue();
            }

            [Fact]
            [Trait("Type", "Interval")]
            public void WhenIntervalIsDegenerate_ReturnsFalse()
            {
                var interval = new Interval(1, 1);

                interval.IsProper.Should().BeFalse();
            }
        }

        public class InteriorProperty
        {
            [Fact]
            [Trait("Type", "Interval")]
            public void ReturnsIntervalWithLargerLowerBound()
            {
                var interval = new Interval(0, 10);

                interval.Interior.X.Should().BeGreaterThan(interval.X);
            }

            [Fact]
            [Trait("Type", "Interval")]
            public void ReturnsIntervalWithSmallerUpperBound()
            {
                var interval = new Interval(0, 10);

                interval.Interior.Y.Should().BeLessThan(interval.Y);
            }
        }

        public class ClosureProperty
        {
            [Fact]
            [Trait("Type", "Interval")]
            public void ReturnsIntervalWithSmallerLowerBound()
            {
                var interval = new Interval(0, 10);

                interval.Closure.X.Should().BeLessThan(interval.X);
            }

            [Fact]
            [Trait("Type", "Interval")]
            public void ReturnsIntervalWithLargerUpperBound()
            {
                var interval = new Interval(0, 10);

                interval.Closure.Y.Should().BeGreaterThan(interval.Y);
            }
        }

        public class EqualityOperator
        {
            [Fact]
            [Trait("Type", "Interval")]
            public void WhenGivenEqualValue_ReturnsTrue()
            {
                var interval1 = new Interval(0, 1);
                var interval2 = new Interval(0, 1);

                (interval1 == interval2).Should().BeTrue();
            }

            [Fact]
            [Trait("Type", "Interval")]
            public void WhenGivenDifferentValue_ReturnsFalse()
            {
                var interval1 = new Interval(0, 1);
                var interval2 = new Interval(0, 2);

                (interval1 == interval2).Should().BeFalse();
            }
        }

        public class InequalityOperator
        {
            [Fact]
            [Trait("Type", "Interval")]
            public void WhenGivenEqualValue_ReturnsFalse()
            {
                var interval1 = new Interval(0, 1);
                var interval2 = new Interval(0, 1);

                (interval1 != interval2).Should().BeFalse();
            }

            [Fact]
            [Trait("Type", "Interval")]
            public void WhenGivenDifferentValue_ReturnsTrue()
            {
                var interval1 = new Interval(0, 1);
                var interval2 = new Interval(0, 2);

                (interval1 != interval2).Should().BeTrue();
            }
        }

        public class GetObjectDataMethod
        {
            [Fact]
            [Trait("Type", "Interval")]
            public void WhenGivenSerializationInfo_PopulatesIt()
            {
                ISerializable instance = new Interval(1, 10);

                var serializationInfo = new SerializationInfo(typeof(Interval), new FormatterConverter());

                instance.GetObjectData(serializationInfo, new StreamingContext());

                serializationInfo.GetInt32("X").Should().Be(1);
                serializationInfo.GetInt32("Y").Should().Be(10);
            }
        }
    }

    // ReSharper restore InconsistentNaming
}